package com.dd.common.aspect;



import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.dd.common.aspect.annotation.LogOperation;
import com.dd.common.entity.SysOperateLog;
import com.dd.common.enums.RequestType;
import com.dd.common.mapper.SysOperateLogMapper;
import com.dd.common.utils.IpUtils;
import com.dd.common.utils.RequestContextUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 操作日志切面（优化版）
 * 支持异步记录、脱敏、异常堆栈、资源释放、可配置开关
 * @author cheng
 */
@Aspect
@Component
@Order(1) // 控制切面执行顺序，确保在事务等切面之前执行
public class LogOperationAspect {

    @Resource
    private SysOperateLogMapper systemLogOperateMapper;

    private static final Logger log = LoggerFactory.getLogger(LogOperationAspect.class);

    /**
     * 是否启用日志记录（可通过配置文件控制）
     */
    @Value("${log.aspect.enabled:true}")
    private boolean logEnabled;

    /**
     * 是否异步记录日志（生产环境建议开启）
     */
    @Value("${log.aspect.async:true}")
    private boolean asyncEnabled;

    /**
     * 线程本地变量：记录方法开始时间
     */
    private static final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();

    /**
     * 自定义线程池：用于异步写日志
     * 使用有界队列防止 OOM，命名线程便于排查
     */
    private final ExecutorService logExecutor = new ThreadPoolExecutor(
            1, 2, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            r -> new Thread(r, "log-record-thread"),
            new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行，避免丢日志
    );

    /**
     * 切入点：拦截带有 @LogOperation 注解的方法
     */
    @Pointcut("@annotation(com.dd.common.aspect.annotation.LogOperation)")
    private void pointCutMethodController() {}

    /**
     * 环绕通知：记录正常执行日志
     */
    @Around("pointCutMethodController()")
    public Object doAroundService(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!logEnabled) {
            return joinPoint.proceed();
        }

        long startTime = System.currentTimeMillis();
        startTimeHolder.set(startTime);

        try {
            Object result = joinPoint.proceed();
            recordLog(joinPoint, null);
            return result;
        } finally {
            startTimeHolder.remove(); // 👉 防止内存泄漏
        }
    }

    /**
     * 异常通知：记录异常日志
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, LogOperation controllerLog, Exception e) {
        if (!logEnabled) {
            return;
        }
        if (controllerLog.ignoreException()) {
            // 某些业务异常（如用户不存在）是正常流程，不需要记错，如用户不存在等
            log.debug("忽略异常日志记录：{}", controllerLog.title());
            return;
        }
        recordLog(joinPoint, e);
    }

    /**
     * 记录日志核心方法
     */
    private void recordLog(Object joinPointObj, final Exception e) {
        try {
            long beginTime = startTimeHolder.get();
            long endTime = System.currentTimeMillis();
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                log.warn("无法获取请求上下文，跳过日志记录");
                return;
            }

            HttpServletRequest request = requestAttributes.getRequest();
//            Integer adminId = LikeAdminThreadLocal.getAdminId();
            Long adminId = StpUtil.getLoginIdAsLong();

            ProceedingJoinPoint joinPoint = (ProceedingJoinPoint) joinPointObj;
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            LogOperation logAnnotation = method.getAnnotation(LogOperation.class);

            if (logAnnotation == null) {
                log.warn("未找到 @Log 注解，跳过记录");
                return;
            }

            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();

            // 获取并脱敏参数
            String params = extractAndMaskParams(joinPoint, request, logAnnotation);

            // 错误信息处理
            String error = "";
            int status = 1;
            if (e != null) {
                error = getFullExceptionMessage(e); // 👉 记录完整堆栈
                // 截断到 900 字符（根据数据库字段长度调整）
                if (error.length() > 900) {
                    error = error.substring(0, 900) + " ...[TRUNCATED]";
                }
                status = 0;
                log.warn("操作日志记录异常：{} - {}", className + "." + methodName, e.getMessage());
            }

            // 构建日志模型
            SysOperateLog model = new SysOperateLog();
            model.setUserId(adminId);
            model.setTitle(logAnnotation.title());
            model.setIp(IpUtils.getIp());
            model.setRequestMethod(request.getMethod());
            model.setMethod(className + "." + methodName + "()");
            model.setUrl(RequestContextUtils.route());
            model.setParams(params);
            model.setErrorMsg(error);
            model.setStatus(status);
//            model.setStartTime(beginTime / 1000);
//            model.setEndTime(endTime / 1000);
//            model.setTaskTime(endTime - beginTime);
//            model.setCreateTime(System.currentTimeMillis() / 1000);

            // 异步或同步写入数据库
            Runnable task = () -> {
                try {
                    systemLogOperateMapper.insert(model);
                    log.debug("操作日志写入成功：{}", model.getTitle());
                } catch (Exception ex) {
                    log.error("操作日志写入数据库失败", ex);
                }
            };

            if (asyncEnabled) {
                logExecutor.submit(task);
            } else {
                task.run(); // 同步执行，便于调试
            }

        } catch (Exception ex) {
            log.error("记录操作日志时发生异常", ex);
        }
    }

    /**
     * 提取并脱敏请求参数
     */
    private String extractAndMaskParams(ProceedingJoinPoint joinPoint, HttpServletRequest request, LogOperation logAnnotation) {
        if (!logAnnotation.recordParams()) {
            return ""; // 不记录参数
        }
        String method = request.getMethod();
        Object[] args = joinPoint.getArgs();

        if (args == null || args.length == 0) {
            return "GET".equals(method) ? request.getQueryString() : "";
        }

        if ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method) ) {
            if (Objects.equals(RequestType.File, logAnnotation.requestType())) {
                if (args[0] instanceof StandardMultipartHttpServletRequest) {
                    StandardMultipartHttpServletRequest multipartRequest = (StandardMultipartHttpServletRequest) args[0];
                    return multipartRequest.getMultiFileMap().values().stream()
                            .flatMap(list -> list.stream().map(MultipartFile::getOriginalFilename))
                            .collect(Collectors.joining(","));
                }
                return "[文件上传参数]";
            } else {
                String json = JSON.toJSONString(args);
                return maskSensitiveData(json); // 👉 脱敏处理
            }
        } else if ("GET".equals(method) || "DELETE".equals(method)) {
            return maskSensitiveData(request.getQueryString());
        }

        return "";
    }

    /**
     * 脱敏敏感数据（示例：手机号、身份证、密码等）
     * 可根据业务需求扩展正则表达式
     */
    private String maskSensitiveData(String data) {
        if (data == null) {
            return "";
        }

        // 手机号脱敏：138****1234
        data = data.replaceAll("(1[3-9]\\d)(\\d{4})(\\d{4})", "$1****$3");
        // 身份证脱敏：110101********123X
        data = data.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2");
        // 密码字段脱敏
        data = data.replaceAll("(\"password\"\\s*:\\s*\")[^\"]*(\")", "$1******$2");
        data = data.replaceAll("(\"pwd\"\\s*:\\s*\")[^\"]*(\")", "$1******$2");

        return data;
    }

    /**
     * 获取完整的异常堆栈信息
     */
    private String getFullExceptionMessage(Exception e) {
        if (e == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        sb.append(e.toString()).append("\n");
        for (StackTraceElement element : e.getStackTrace()) {
            sb.append("\tat ").append(element.toString()).append("\n");
        }
        return sb.toString();
    }

    /**
     * Spring 容器销毁时关闭线程池
     */
    @PreDestroy
    public void shutdown() {
        log.info("正在关闭日志记录线程池...");
        logExecutor.shutdown();
        try {
            if (!logExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
                logExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            logExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        log.info("日志记录线程池已关闭");
    }
}
